﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Hack.Pdb2Mdb
{
    public class PdbWriter
    {
        public PdbWriter(Stream writer, int pageSize)
        {
            this.pageSize = pageSize;
            this.usedBytes = pageSize * 3;
            this.writer = writer;

            writer.SetLength(usedBytes);
        }

        public void WriteMeta(DataStream[] streams, BitAccess bits)
        {
            PdbFileHeader head = new PdbFileHeader(pageSize);

            WriteDirectory(streams,
                           out head.directoryRoot,
                           out head.directorySize,
                           bits);
            WriteFreeMap();

            head.freePageMap = 2;
            head.pagesUsed = usedBytes / pageSize;

            writer.Seek(0, SeekOrigin.Begin);
            head.Write(writer, bits);
        }

        private void WriteDirectory(DataStream[] streams,
                                    out int directoryRoot,
                                    out int directorySize,
                                    BitAccess bits)
        {
            DataStream directory = new DataStream();

            int pages = 0;
            for (int s = 0; s < streams.Length; s++)
            {
                if (streams[s].Length > 0)
                {
                    pages += streams[s].Pages;
                }
            }

            int use = 4 * (1 + streams.Length + pages);
            bits.MinCapacity(use);
            bits.WriteInt32(streams.Length);
            for (int s = 0; s < streams.Length; s++)
            {
                bits.WriteInt32(streams[s].Length);
            }
            for (int s = 0; s < streams.Length; s++)
            {
                if (streams[s].Length > 0)
                {
                    bits.WriteInt32(streams[s].pages);
                }
            }
            directory.Write(this, bits.Buffer, use);
            directorySize = directory.Length;

            use = 4 * directory.Pages;
            bits.MinCapacity(use);
            bits.WriteInt32(directory.pages);

            DataStream ddir = new DataStream();
            ddir.Write(this, bits.Buffer, use);

            directoryRoot = ddir.pages[0];
        }

        private void WriteFreeMap()
        {
            byte[] buffer = new byte[pageSize];

            // We configure the old free map with only the first 3 pages allocated.
            buffer[0] = 0xf8;
            for (int i = 1; i < pageSize; i++)
            {
                buffer[i] = 0xff;
            }
            Seek(1, 0);
            Write(buffer, 0, pageSize);

            // We configure the new free map with all of the used pages gone.
            int count = usedBytes / pageSize;
            int full = count / 8;
            for (int i = 0; i < full; i++)
            {
                buffer[i] = 0;
            }
            int rema = count % 8;
            buffer[full] = (byte)(0xff << rema);

            Seek(2, 0);
            Write(buffer, 0, pageSize);
        }

        public int AllocatePages(int count)
        {
            int begin = usedBytes;

            usedBytes += count * pageSize;
            writer.SetLength(usedBytes);

            if (usedBytes > pageSize * pageSize * 8)
            {
                throw new Exception("PdbWriter does not support multiple free maps.");
            }
            return begin / pageSize;
        }

        public void Seek(int page, int offset)
        {
            writer.Seek(page * pageSize + offset, SeekOrigin.Begin);
        }

        public void Write(byte[] bytes, int offset, int count)
        {
            writer.Write(bytes, offset, count);
        }

        //////////////////////////////////////////////////////////////////////
        //
        public int PageSize
        {
            get { return pageSize; }
        }

        //////////////////////////////////////////////////////////////////////
        //
        public readonly int pageSize;
        private Stream writer;
        private int usedBytes;
    }
}
